﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ComputerArchitecture.Core;
using ComputerArchitecture.Core.Logging;
using ComputerArchitecture.Numbers;
using ComputerArchitecture.Units.Integers;
using ComputerArchitecture.Units.Integers.Signed.Implementations;
using ComputerArchitecture.Units.Integers.Signed.Interfaces;
using ComputerArchitecture.Units.Interfaces;

namespace ComputerArchitecture.Units.Float
{
    public class FloatAdderUnit : LoggingObject, IFloatAdderUnit
    {
        #region Fields

        private readonly ISignedCompareUnit _compareUnit;
        private readonly ISignedAdderUnit _adderUnit;
        private readonly ISignedNegateUnit _negateUnit;
        private readonly ISignedSubstractUnit _substractUnit;

        #endregion

        #region Constructors

        public FloatAdderUnit(ILoggingService loggingService, ISignedCompareUnit compareUnit, ISignedAdderUnit adderUnit, ISignedNegateUnit negateUnit, ISignedSubstractUnit substractUnit)
            : base(loggingService)
        {
            ArgumentValidator.NullValidate(compareUnit);
            _compareUnit = compareUnit;
            ArgumentValidator.NullValidate(adderUnit);
            _adderUnit = adderUnit;
            ArgumentValidator.NullValidate(negateUnit, "negateUnit");
            _negateUnit = negateUnit;
            ArgumentValidator.NullValidate(substractUnit, "substractUnit");
            _substractUnit = substractUnit;
        }

        public FloatAdderUnit(ILoggingService loggingService)
            : this(loggingService, new SignedCompareUnit(loggingService), new SignedAdderUnit(loggingService), new SignedNegateUnit(loggingService), new SignedSubstractUnit(loggingService))
        {
        }

        #endregion

        #region Methods

        private static IUnsignedBitNumber GetUnsignedOffset(int size)
        {
            IUnsignedBitNumber v = new UnsignedBitNumber(size);
            for (int i = 1; i < size; ++i)
            {
                v[i].Set();
            }
            return v;
        }

        private static ISignedBitNumber GetSignedOffset(int size)
        {
            ISignedBitNumber v = new SignedBitNumber(size);
            for (int i = 1; i < size; ++i)
            {
                v[i].Set();
            }
            return v;
        }

        private static ISignedBitNumber MantissaToSigned(IUnsignedBitNumber mantissaWithImplicit, Bit sign, out bool hasError)
        {
            ISignedBitNumber value = new SignedBitNumber(mantissaWithImplicit.Size + 1);
            mantissaWithImplicit.CopyTo(value, 1, 0, mantissaWithImplicit.Size);
            hasError = false;
            if (sign.IsSet)
            {
                ILoggingService log = new NLogFacadeLoggingService();
                log.DisableLogging();
                NegateSpecialCase specialCase;
                value = new SignedNegateUnit(log).Negate(value, out specialCase, out hasError);
                log.EnableLogging();
            }
            return value;
        }

        private static IUnsignedBitNumber RemoveFirstBit(IBitNumber mantissa)
        {
            IUnsignedBitNumber value = new UnsignedBitNumber(mantissa.Size - 1);
            mantissa.CopyTo(value, 0, 1, value.Size);
            return value;
        }

        public IFloatPointNumber Add(IFloatPointNumber first, IFloatPointNumber second)
        {
            if (ReferenceEquals(first, null))
            {
                throw new ArgumentNullException("first");
            }
            if (ReferenceEquals(second, null))
            {
                throw new ArgumentNullException("second");
            }
            if (first.IsNaN || second.IsNaN)
            {
                return FloatPointNumber.GetQuietNaN(first.Exponent.Size, first.Mantissa.Size);
            }
            if (first.IsInfinity)
            {
                if (first.IsPositiveInfinity)
                {
                    // +inf + (-inf) = nan
                    if (second.IsNegativeInfinity)
                    {
                        return FloatPointNumber.GetQuietNaN(first.Exponent.Size, first.Mantissa.Size);
                    }
                    return FloatPointNumber.GetPositiveInfinity(first.Exponent.Size, first.Mantissa.Size);
                }
                if (second.IsPositiveInfinity)
                {
                    return FloatPointNumber.GetQuietNaN(first.Exponent.Size, first.Mantissa.Size);
                }
                return FloatPointNumber.GetNegativeInfinity(first.Exponent.Size, first.Mantissa.Size);
            }
            if (second.IsInfinity)
            {
                if (second.IsPositiveInfinity)
                {
                    // +inf + (-inf) = nan
                    if (first.IsNegativeInfinity)
                    {
                        return FloatPointNumber.GetQuietNaN(first.Exponent.Size, first.Mantissa.Size);
                    }
                    return FloatPointNumber.GetPositiveInfinity(first.Exponent.Size, first.Mantissa.Size);
                }
                if (first.IsPositiveInfinity)
                {
                    return FloatPointNumber.GetQuietNaN(first.Exponent.Size, first.Mantissa.Size);
                }
                return FloatPointNumber.GetNegativeInfinity(first.Exponent.Size, first.Mantissa.Size);
            }
            if (first.IsZero)
            {
                return (IFloatPointNumber) second.Clone();
            }
            if (second.IsZero)
            {
                return (IFloatPointNumber) first.Clone();
            }

            this.LoggingService.LogInfo("Get complement values of exponents");
            ISignedBitNumber firstExp = first.Exponent.ToComplement(GetUnsignedOffset(first.Exponent.Size)),
                             secondExp = second.Exponent.ToComplement(GetUnsignedOffset(first.Exponent.Size));
            this.LoggingService.LogInfo("First omplement exponent: {0}", firstExp);
            this.LoggingService.LogInfo("Second complement exponent: {0}", secondExp);
            IUnsignedBitNumber firstMantissa = first.GetImplicitMantissa(),
                               secondMantissa = second.GetImplicitMantissa();
            this.LoggingService.LogInfo("Get first mantissa with implicit bit: {0}", firstMantissa);
            this.LoggingService.LogInfo("Get second mantissa with implicit bit: {0}", secondMantissa);
            
            // выравниваем порядки
            this.LoggingService.LogInfo("Compare exponents");
            int compareResult = _compareUnit.Compare(firstExp, secondExp);
            bool overflow;
            while (compareResult != 0)
            {
                Bit carryBit;
                // get less
                if (compareResult == 1)
                {
                    this.LoggingService.LogInfo("Second exponent is less");
                    // less is second;
                    this.LoggingService.LogInfo("Shift right second mantissa");
                    secondMantissa.LogicShiftRight(1, out carryBit);
                    if (secondMantissa.IsZero)
                    {
                        this.LoggingService.LogWarning("Second mantissa is zero. Return first value");
                        return (IFloatPointNumber)first.Clone();
                    }
                    this.LoggingService.LogInfo("New second mantissa value: {0}", secondMantissa);
                    this.LoggingService.LogInfo("Inc second exponent");
                    secondExp = _adderUnit.Inc(secondExp, out overflow);
                    this.LoggingService.LogInfo("New second exponent value: {0}\n", secondExp);
                }
                else
                {
                    this.LoggingService.LogInfo("First number is less");
                    // first is less
                    this.LoggingService.LogInfo("Shift right first mantissa");
                    firstMantissa.LogicShiftRight(1, out carryBit);
                    if (firstMantissa.IsZero)
                    {
                        this.LoggingService.LogWarning("First mantissa is zero. Return second value");
                        return (IFloatPointNumber)second.Clone();
                    }
                    firstExp = _adderUnit.Inc(firstExp, out overflow);
                    
                }
                compareResult = _compareUnit.Compare(firstExp, secondExp);
            }
            // складываем мантиссы с неявным битом в доп. коде
            bool firstError = false, secondError = false;
            ISignedBitNumber firstSignedMantissa = FloatAdderUnit.MantissaToSigned(firstMantissa, first.SignBit,
                                                                                   out firstError),
                             secondSignedMantissa = MantissaToSigned(secondMantissa, second.SignBit, out secondError);
            if (firstError || secondError)
            {
                throw new Exception("can't create signed mantissa");
            }

            var resultMantissa = _adderUnit.Add(firstSignedMantissa, secondSignedMantissa, out overflow);
            // если есть переполнение - увеличить exp
            ISignedBitNumber resultExp = (ISignedBitNumber) firstExp.Clone();
            if (overflow)
            {
                bool hasExponentOverflow;
                Bit carryBit;
                resultExp = _adderUnit.Inc(resultExp, out hasExponentOverflow);
                resultMantissa.LogicShiftRight(1, out carryBit);
                if (hasExponentOverflow)
                {
                    throw new Exception("exponent overflow");
                }
            }
            Bit resultSign = Bit.Zero;
            resultSign.SetAs(resultMantissa.SignBit);
            if (resultSign.IsSet)
            {
                NegateSpecialCase negate;
                bool error;
                resultMantissa = _negateUnit.Negate(resultMantissa, out negate, out error);
            }
            // проверка нормализации результата
            // remove signed bit
            var clearMantissa = RemoveFirstBit(resultMantissa);
            
            while(!clearMantissa[0].IsSet)
            {
                Bit carryBit;
                clearMantissa.LogicShiftLeft(1, out carryBit);
                resultExp = _substractUnit.Dec(resultExp, out overflow);
            }
            // remove first bit
           
            return new FloatPointNumber(resultSign, resultExp.ToOffseted(GetSignedOffset(resultExp.Size)), RemoveFirstBit(clearMantissa));

            throw new NotImplementedException();
        }

        #endregion
    }
}
